import path from "path"
import { parse, compileScript } from '@vue/compiler-sfc'
import { Project } from "ts-morph"
import FastGlob from "fast-glob"
import fs from "fs"

class PluginUtil {
  #project: Project

  #options: VitePluginBuildTypesOptions

  #index: number = 1

  #aimFiles: Array<any> = []

  #sourceFiles: Array<any> = []

  constructor(options: VitePluginBuildTypesOptions) {
    this.#options = options
    this.#aimFiles = []
    this.#sourceFiles = []
    this.#index = 1
    this.#project = new Project({
      compilerOptions: {
        declaration: true,
        emitDeclarationOnly: true,
        noEmit: false,
        noEmitOnError: true,
        allowJs: true, // 若是想兼容 js 语法须要加上
        outDir: this.#options.aim // 能够设置自定义的打包文件夹，如 'types'
      },
      tsConfigFilePath: path.resolve(this.#options.root, './tsconfig.json'),
      skipAddingFilesFromTsConfig: true
    })
  }

  async compilerVueScript(file: any) {
    // 对于 vue 文件，借助 @vue/compiler-sfc 的 parse 进行解析
    const sfc = parse(await fs.promises.readFile(file, 'utf-8'))
    // 提取出 script 中的内容
    const { script, scriptSetup } = sfc.descriptor

    if (script || scriptSetup) {
      let content = ''
      let isTs = false

      if (script && script.content) {
        content += script.content

        if (script.lang === 'ts') isTs = true
      }

      if (scriptSetup) {
        const compiled = compileScript(sfc.descriptor, {
          id: `${this.#index++}`
        })

        content += compiled.content

        if (scriptSetup.lang === 'ts') {
          isTs = true
        }
      }
      return this.#project.createSourceFile(file + (isTs ? '.ts' : '.js'), content)
    }

    return null
  }
  async packFiles() {
    // 随后将解析完的文件写道打包路径
    for (const sourceFile of this.#aimFiles) {
      const emitOutput = sourceFile.getEmitOutput()
      for (const outputFile of emitOutput.getOutputFiles()) {
        const filePath = outputFile.getFilePath()
        await fs.promises.mkdir(path.dirname(filePath), { recursive: true })
        await fs.promises.writeFile(filePath, outputFile.getText(), 'utf8')
        console.log(" -- success write file =>", filePath)
      }
    }
  }
  async build() {

    let path = []
    // 路径中必须使用 / ，不能使用 \\
    if (typeof this.#options.source == "string") {
      path.push((this.#options.source + "/**/*.ts").replaceAll("\\", "/"))
      path.push((this.#options.source + "/**/*.vue").replaceAll("\\", "/"))
    }
    else {
      for (const item of this.#options.source) {
        path.push((item + "/**/*.ts").replaceAll("\\", "/"))
        path.push((item + "/**/*.vue").replaceAll("\\", "/"))
      }
    }
    this.#sourceFiles = await FastGlob(path)

    await Promise.all(
      this.#sourceFiles.map(async file => {
        if (/\.vue$/.test(file)) {
          const aim_file = await this.compilerVueScript(file)
          if (aim_file) {
            this.#aimFiles.push(aim_file)
          }
        } else {
          // 若是是 ts 文件则直接添加便可
          this.#aimFiles.push(this.#project.addSourceFileAtPath(file))
        }
      })
    )

    const diagnostics = this.#project.getPreEmitDiagnostics()
    // 输出解析过程当中的错误信息
    const errorMsg = this.#project.formatDiagnosticsWithColorAndContext(diagnostics)
    if (errorMsg) {
      console.log("Error =>", errorMsg)
    }

    this.#project.emitToMemory()

    await this.packFiles()

  }

}
function build(options: VitePluginBuildTypesOptions) {
  const util = new PluginUtil(options)
  util.build()
}

declare type VitePluginBuildTypesOptions = {
  root: string
  source: string | string[]
  aim: string
}

export default function (options: VitePluginBuildTypesOptions) {
  return {
    name: "vite-plugin-guild-types",
    apply: "build",
    buildStart() {
      // 解析每个路径的源代码进行转换输出
      build(options)
    }
  }
}